import { eval_helper } from './eval_helper.js'
import { domain_helper } from './domain_helper.js'

function eval_modifiers(modifiers, { editable, record, context }) {
  if (typeof modifiers === 'boolean') {
    return modifiers
  } else if (typeof modifiers === 'number') {
    return modifiers ? true : false
  } else if (typeof modifiers === 'string') {
    const me = Number(modifiers)
    if (Number.isNaN(me)) {
      console.log('eval_modifiers. doing', modifiers, typeof modifiers)
      throw 'error modifiers string: ' + JSON.stringify(modifiers)
    } else {
      return modifiers ? true : false
    }
  } else if (typeof modifiers === 'function') {
    console.log('eval_modifiers. doing', modifiers, typeof modifiers)
    throw 'error modifiers: ' + JSON.stringify(modifiers)
  } else if (Array.isArray(modifiers)) {
    // console.log('eval_modifiers. Array', modifiers, typeof modifiers)
    return eval_helper.eval_array(modifiers, { editable, record, context })
  } else if (typeof modifiers === 'undefined') {
    console.log('eval_modifiers. undefined', modifiers, typeof modifiers)
    return modifiers
  } else if (typeof modifiers === 'object') {
    // console.log('eval_modifiers. object', modifiers)
    return eval_helper.eval_object(modifiers, { editable, record, context })
    // throw 'error modifiers: object' + modifiers
  } else {
    console.log('eval_modifiers. doing', modifiers, typeof modifiers)
    throw 'error modifiers: ' + JSON.stringify(modifiers)
  }
}

function check_groups(groups, { user_groups }) {
  if (!groups) {
    return true
  }

  // console.log(groups, user_groups)

  // if (groups === 'base.group_no_one') {
  //   console.log(groups, user_groups)
  // }

  const res = groups.split(',').reduce((acc, cur) => {
    return acc && user_groups.includes(cur)
  }, true)

  return res
}

function check_invisible(nodeinfo, kwargs) {
  const { for_view, user_groups, editable, record, context } = kwargs

  if ('groups' in nodeinfo) {
    if (!check_groups(nodeinfo.groups, { user_groups })) {
      return true
    }
  }

  if (['form.data', 'form.view', 'o2m.form'].includes(for_view)) {
    if ('invisible' in nodeinfo) {
      // 无数据时  所有 invisible  一律不显示
      // 有数据后, 再计算 invisible
      // 'form.data' : 'form.view'
      if (for_view === 'form.view') {
        return true
      } else if (['form.data', 'o2m.form'].includes(for_view)) {
        // console.log('check_invisible. todo', nodeinfo)

        return eval_modifiers(nodeinfo.invisible, { editable, record, context })
      } else {
        return true
      }
    } else {
      return false
    }
  } else if (['o2m.tree'].includes(for_view)) {
    if ('column_invisible' in nodeinfo) {
      // console.log('check_invisible =================', nodeinfo, attrs, kws)
      // console.log('check_invisible. todo', nodeinfo, attrs.column_invisible)

      return eval_modifiers(nodeinfo.column_invisible, {
        editable,
        record,
        context
      })
    } else {
      return false
    }
  } else if (['tree'].includes(for_view)) {
    if ('column_invisible' in nodeinfo) {
      return eval_modifiers(nodeinfo.column_invisible, {
        editable,
        record,
        context
      })
    } else {
      return false
    }
  } else if (['search'].includes(for_view)) {
    if ('invisible' in nodeinfo) {
      return eval_modifiers(nodeinfo.invisible, { editable, record, context })
    } else {
      return false
    }
  } else {
    return true
  }

  // if ('invisible' in nodeinfo) {
  //   return eval_modifiers(nodeinfo.invisible, { editable, record, context })
  // }

  // if ('column_invisible' in nodeinfo) {
  //   return eval_modifiers(nodeinfo.column_invisible, {
  //     editable,
  //     record,
  //     context
  //   })
  // }
}

function check_readonly(nodeinfo, { meta, editable, record, context }) {
  // nodeinfo 中:
  // required: 用于字段编辑. 依次取自 nodeinfo, meta
  // readonly: 用于字段编辑. 依次取自 nodeinfo, meta
  // meta.readonly 结合 meta.states 控制 write 函数的 value
  //
  // 第一次 无 record时,
  //  required = false, readonly=true
  // 第二次 有 record, 只读, 未编辑时:
  //  required = false, readonly=true
  // 第三次 有 record, 编辑时: 依次取:
  //  2. nodeinfo.required/readonly
  //  3. meta.readonly/required

  if (!editable) {
    return true
  }

  const modifiers = 'readonly'
  if (modifiers in nodeinfo) {
    // console.log('check_readonly. todo', nodeinfo[modifiers])
    return eval_modifiers(nodeinfo[modifiers], { record, context })
  }

  if (modifiers in meta) {
    return eval_modifiers(meta[modifiers], { record, context })
  }
}

function check_required(nodeinfo, { meta, editable, record, context }) {
  if (!editable) {
    return false
  }

  const modifiers = 'required'

  if (modifiers in nodeinfo) {
    // console.log('check_required2. todo', nodeinfo)

    return eval_modifiers(nodeinfo[modifiers], { record, context })
  }

  if (modifiers in meta) {
    return eval_modifiers(meta[modifiers], { record, context })
  }

  return undefined
}

function get_domain(nodeinfo, { ...kws }) {
  // const { env, meta, record, context } = kws
  const { meta } = kws
  const modifiers = 'domain'
  if (!(modifiers in meta)) {
    return
  }

  function domain_get() {
    const domain_raw =
      modifiers in nodeinfo ? nodeinfo[modifiers] : meta[modifiers]

    return domain_helper.domain_maker(domain_raw, { ...kws })
  }

  const domain_raw = domain_get()

  return domain_raw
}

function context_get(ctx, kws = {}) {
  // const { context, record } = kws

  // console.log(' context_get ', ctx, kws)

  const ctx2 = Object.keys(ctx).reduce((acc, cur) => {
    const todo = ctx[cur]
    if (['string', 'number', 'boolean'].includes(typeof todo)) {
      acc[cur] = todo
    } else if (Array.isArray(todo)) {
      acc[cur] = todo
    } else if (typeof todo === 'object') {
      acc[cur] = eval_helper.eval_object(todo, kws)
    } else {
      // todo :
      // default_currency_id: currency_id or company_currency_id,

      console.log('context_get. doing', ctx, cur, todo)
      throw 'error, context_get' + JSON.stringify(ctx)
    }

    return acc
  }, {})

  // console.log(' context_get 2', ctx2)

  return ctx2
}

function split_name(str) {
  const ns = str.split('_')
  const [tagname, ...tagname22] = ns
  const tagname2 = tagname22.join('_')
  const fieldname = tagname2.split('__')[0]
  return [tagname, fieldname]
}

const node_helper = {
  get_nodeinfo(node, nodename, kwargs = {}) {
    const { is_one_row_children } = kwargs

    const nodeinfo = node[nodename]
    if (is_one_row_children && typeof nodeinfo !== 'string') {
      nodeinfo.one_row = 1
    }

    return nodeinfo
  },

  parse_node(node, kwargs = {}) {
    const { last_nodename = 'sheet' } = kwargs
    const { for_view } = kwargs

    return Object.keys(node).reduce((acc, nodename) => {
      const [tagname, fname] = split_name(nodename)
      // if (for_view === 'search') {
      //   console.log('parse_node11', tagname, fname)
      // }

      //   console.log('parse_node11', for_view, tagname, fname)

      const nodeinfo = this.get_nodeinfo(node, nodename, kwargs)
      const kws1 = { fname, tagname, nodename, nodeinfo }
      if (tagname === 'unknow') {
        //
      } else if (typeof node[nodename] === 'string') {
        acc[nodename] = this.parse_node_string(node, nodename, kwargs)
      } else if (for_view === 'search' && tagname === 'field') {
        const invisible = check_invisible(nodeinfo, kwargs)
        if (!invisible) {
          acc[nodename] = this.parse_node_search_field(kws1, kwargs)
        }
      } else if (for_view === 'search' && tagname === 'groupby') {
        // todo invisible
        acc[nodename] = this.parse_node_search_groupby(kws1, kwargs)
      } else if (for_view === 'search' && tagname === 'filter') {
        acc[nodename] = this.parse_node_search_filter(kws1, kwargs)
      } else if (tagname === 'field') {
        // console.log('field,vis', last_nodename, nodename, nodeinfo)
        const invisible = check_invisible(nodeinfo, kwargs)
        if (!invisible) {
          acc[nodename] = this.parse_node_field(kws1, kwargs)
        }
      } else if (tagname === 'label') {
        const invisible = check_invisible(nodeinfo, kwargs)
        if (!invisible) {
          acc[nodename] = this.parse_node_label(kws1, kwargs)
        }
      } else if (tagname === 'button') {
        const invisible = check_invisible(nodeinfo, kwargs)
        if (!invisible) {
          acc[nodename] = this.parse_node_button(kws1, kwargs)
        }
      } else {
        const all_tags = [
          'card',
          'group',
          'subgroup',
          'notebook',
          'page',
          'widget',
          'button'
        ]
        if (!all_tags.includes(tagname)) {
          // console.log(' parse_node', last_nodename, nodename)
          throw `error parse_node: ${last_nodename}.${nodename}`
        } else {
          const { attr = {}, ...nodeinfo2 } = nodeinfo

          const invisible = check_invisible(attr, kwargs)
          if (!invisible) {
            // console.log(' parse_node else,', last_nodename, nodename, nodeinfo)

            acc[nodename] = this.parse_node_else(kws1, kwargs)
          }
        }
      }

      return acc
    }, {})
  },

  parse_node_string(kws1, kwargs = {}) {
    const { fname, tagname, nodename, nodeinfo } = kws1
    const { is_one_row_children } = kwargs

    // console.log('tag:', last_nodename, nodename, nodeinfo)
    const attr2 = {
      text: nodeinfo
    }
    if (tagname === 'widget') {
      attr2.name = fname
      attr2.one_row = is_one_row_children
    }

    if (is_one_row_children) {
      attr2.one_row = is_one_row_children
    }

    return { ...attr2, tagname, nodename }
  },

  parse_node_search_field(kws1, kwargs = {}) {
    const { fname, tagname, nodename, nodeinfo } = kws1
    const { fields, metadata } = kwargs

    const meta = metadata[fname]

    const nodeinfo2 = { ...nodeinfo }
    delete nodeinfo2.groups

    return {
      meta,
      name: fname,
      string: meta.string,
      ...nodeinfo2,
      tagname,
      nodename
    }
  },

  parse_node_search_groupby(kws1, kwargs = {}) {
    const { fname, tagname, nodename, nodeinfo } = kws1

    return {
      ...nodeinfo,
      tagname,
      nodename
    }
  },

  parse_node_search_filter(kws1, kwargs = {}) {
    const { fname, tagname, nodename, nodeinfo } = kws1
    const { fields, metadata } = kwargs

    const { string, ...nodeinfo2 } = nodeinfo

    const nodeinfo3 = Object.keys(nodeinfo2).reduce((acc, cur) => {
      acc[cur] = { ...nodeinfo2[cur], name: cur }
      return acc
    }, {})

    return {
      string,
      children: { ...nodeinfo3 },
      tagname,
      nodename
    }
  },

  parse_node_field(kws1, kwargs = {}) {
    const { fname, tagname, nodename, nodeinfo } = kws1
    const { fields, metadata } = kwargs

    // string: 用于字段label. 依次取自 nodeinfo, fieldinfo, meta
    // meta: 取自 odoo.metadata
    // widget: 控制前端组件. 取自 nodeinfo,
    // fieldinfo 中 不该再有其他信息了
    // nodeinfo 中 可增加一些控制属性. 如宽高背景色等
    //

    const fieldinfo = fields[fname] || {}
    const meta = metadata[fname]
    const nodeinfo2 = { ...nodeinfo }

    delete nodeinfo2.invisible
    delete nodeinfo2.groups
    delete nodeinfo2.readonly
    delete nodeinfo2.required

    const { env, editable, record, context, record_onchange } = kwargs

    const readonly = check_readonly(nodeinfo, {
      meta,
      editable,
      record,
      context
    })
    const required = check_required(nodeinfo, {
      meta,
      editable,
      record,
      context
    })

    const domain = get_domain(nodeinfo, {
      meta: { ...meta, ...(fields[meta.name] || {}) },
      editable,
      record: record || {},
      context
    })
    const ctx =
      'context' in nodeinfo
        ? context_get(nodeinfo.context, {
            env,
            record: record_onchange || {},
            context
          })
        : undefined

    // todo  label
    return {
      meta,
      name: meta.name,
      string: meta.string,
      ...fieldinfo,
      ...nodeinfo2,
      readonly,
      required,
      ...(domain ? { domain: domain } : {}),
      //   ...(attrs ? { attrs: attrs } : {}),
      ...(ctx ? { context: ctx } : {}),
      tagname,
      nodename
    }
  },

  parse_node_label(kws1, kwargs = {}) {
    const { fname, tagname, nodename, nodeinfo } = kws1
    // const { fields, metadata } = kwargs
    const { last_nodename = 'sheet' } = kwargs

    const nodeinfo2 = { ...nodeinfo }
    delete nodeinfo2.invisible
    delete nodeinfo2.groups
    // delete nodeinfo2.attrs

    const path = `${last_nodename}.${nodename}`

    const children = node_helper.parse_node(nodeinfo2, {
      ...kwargs,
      last_nodename: path
    })

    return { ...nodeinfo2, children, tagname, nodename }
  },

  parse_node_button(kws1, kwargs = {}) {
    const { fname, tagname, nodename, nodeinfo } = kws1
    const { fields, metadata, module } = kwargs
    const { last_nodename = 'sheet' } = kwargs

    const { ...nodeinfo2 } = nodeinfo
    delete nodeinfo2.invisible
    delete nodeinfo2.groups
    // delete nodeinfo2.attrs

    if (nodeinfo2.type === 'action') {
      // console.log('btn,', nodeinfo2.name, module)
      if (nodeinfo2.name.split('.').length === 1) {
        nodeinfo2.name = `${module}.${nodeinfo2.name}`
      }
    }

    const [nodeinfo3, nodeinfo4] = Object.keys(nodeinfo2).reduce(
      (acc2, cur2) => {
        if (['widget', 'field'].includes(cur2.split('_')[0])) {
          acc2[0][cur2] = nodeinfo2[cur2]
        } else {
          acc2[1][cur2] = nodeinfo2[cur2]
        }

        return acc2
      },
      [{}, {}]
    )

    const path = `${last_nodename}.${nodename}`

    const children = node_helper.parse_node(nodeinfo3, {
      ...kwargs,
      last_nodename: path
    })

    return { ...nodeinfo4, children, tagname, nodename }
  },

  parse_node_else(kws1, kwargs = {}) {
    // console.log(' parse_node else,')
    const { fname, tagname, nodename, nodeinfo } = kws1
    // const { fields, metadata, module } = kwargs
    const { last_nodename = 'sheet', is_group_children } = kwargs
    const { is_one_row_children } = kwargs

    const path = `${last_nodename}.${nodename}`

    const { attr = {}, ...nodeinfo2 } = nodeinfo

    // console.log(' parse_node else,', nodeinfo2)
    const children = node_helper.parse_node(nodeinfo2, {
      ...kwargs,
      last_nodename: path,
      is_group_children: is_group_children || tagname === 'group',
      is_one_row_children: fname === 'one_row' || is_one_row_children
    })

    // // attr 可能有  string, name, id, widget
    // // string, 可用于显示文本.
    // // widget, 用于 前端自定义组件
    // // 其他无实际用处
    const attr2 = { ...attr }
    delete attr2.invisible
    delete attr2.groups
    // delete attr2.attrs
    if (tagname === 'widget') {
      attr2.name = fname
    }
    // // tagname 用于 前端判断 通用组件
    return {
      ...attr2,
      children,
      tagname: tagname === 'group' && is_group_children ? 'subgroup' : tagname,
      nodename
    }
  }
}

export function parse_node(node, kwargs = {}) {
  // console.log('parse_node:', node, kwargs)
  const res = node_helper.parse_node(node, kwargs)
  // console.log('parse_node ok', res)
  return res
}

export const fields_helper = {
  merge_fields(dst = {}, src = {}) {
    return Object.keys({ ...dst, ...src }).reduce((acc, fld) => {
      const indst = dst[fld] || {}
      const insrc = src[fld] || {}

      const ok = { ...indst, ...insrc }
      if ('fields' in indst && 'fields' in insrc) {
        ok.fields = this.merge_fields(indst.fields, insrc.fields)
      }
      acc[fld] = ok
      return acc
    }, {})
  },

  fields_get_in_sheet(sheet) {
    function get_node_name(str) {
      if (!str[0] === '_') return false
      const tag = str.split('_')[1]
      return tag
    }

    // treeview 格式
    // sheet:{
    //   field_name: {},
    //   field_name__other_char: {},
    // }
    //
    // formview 格式
    // sheet:{
    //   field_name: {},
    //   field_name__other_char: {},
    //   tagname_other_char: {
    //     attr: {},
    //     field_name: {},
    //     field_name__other_char: {},
    //   },
    // }
    //

    const self = this

    function split_name(str) {
      const ns = str.split('_')
      const [tagname, ...tagname22] = ns
      const tagname2 = tagname22.join('_')
      const fieldname = tagname2.split('__')[0]
      return [tagname, fieldname]
    }

    function find_field(node) {
      if (!node) {
        return {}
      }

      if (typeof node !== 'object') {
        return {}
      }

      return Object.keys(node).reduce((acc, cur) => {
        const [tagname, fname] = split_name(cur)

        if (tagname === 'field') {
          const meta = { ...node[cur] }
          const me = { [fname]: { ...meta } }
          acc = self.merge_fields(acc, me)
        } else if (tagname === 'filter') {
          //
        } else {
          const children = find_field(node[cur])
          acc = { ...acc, ...children }
        }

        return acc
      }, {})
    }

    const fields = find_field(sheet)
    return fields
  },

  fields_get_in_views(views) {
    const fs_in_views = Object.keys(views).reduce((acc, viewtype) => {
      const vi = views[viewtype]
      const { arch = {} } = vi

      // const { fields: fs_vi = {} } = vi
      // const { fields: fs_arch = {} } = arch
      // acc[`${viewtype}.vi`] = fs_vi
      // acc[`${viewtype}.arch`] = fs_arch

      const { button_box = {}, header = {}, sheet = {}, kanban = {} } = arch

      acc[`${viewtype}.box`] = this.fields_get_in_sheet(button_box)
      acc[`${viewtype}.header`] = this.fields_get_in_sheet(header)
      acc[`${viewtype}.sheet`] = this.fields_get_in_sheet(sheet)
      acc[`${viewtype}.kanban`] = this.fields_get_in_sheet(kanban)
      return acc
    }, {})

    const fields = Object.keys(fs_in_views).reduce((acc, cur) => {
      const fs = fs_in_views[cur]

      Object.keys(fs).forEach(fld => {
        const meta = fs[fld] || {}

        const old = acc[fld] || {}
        acc[fld] = { ...old }

        if (meta.fields) {
          const fields2 = { ...meta.fields }
          if (!('display_name' in fields2)) {
            fields2.display_name = {}
          }
          const me = { [fld]: { fields: fields2 } }
          acc = this.merge_fields(acc, me)
        }

        if (meta.views) {
          const views2 = Object.keys(meta.views).reduce((acc2, cur2) => {
            acc2[`${cur}.${cur2}`] = meta.views[cur2]
            return acc2
          }, {})

          const me_fields = this.fields_get_in_views(views2)

          if (Object.keys(me_fields).length) {
            const me = { [fld]: { fields: { ...me_fields } } }
            acc = this.merge_fields(acc, me)
          }
        }
      })

      return acc
    }, {})

    return fields
  }
}
